package icasue.net.socket.driver.mdc;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import icasue.net.base.annotations.NotNull;
import icasue.net.base.annotations.Nullable;
import icasue.net.protocol.carfun.common.CommandResponse;
import icasue.net.socket.dto.md.channel.ChannelCreateDTO;
import icasue.net.socket.dto.md.channel.ChannelJoinDTO;
import icasue.net.socket.exception.ClientConnectException;
import icasue.net.socket.exception.CommandExecException;
import icasue.net.socket.exception.ProtocolUnSupportException;
import icasue.net.socket.model.md.channel.ChannelJoin;
import icasue.net.socket.model.md.channel.ChannelQuit;
import icasue.net.socket.model.register.RegisterInfo;
import icasue.net.socket.share.MindManger;
import icasue.net.socket.thread.Channel;
import icasue.net.socket.thread.SocketProxy;
import icasue.net.socket.thread.state.ChannelState;
import icasue.net.socket.thread.state.SocketState;
import icasue.net.socket.utils.Assert;
import icasue.net.socket.utils.PoolUtil;
import icasue.net.protocol.carfun.commands.MDCommand;

import java.util.Map;

/**
 * @Author: Qiao Hang
 * @CreateDate: 2021/3/19 下午3:36
 * @UpdateDate:
 * @Description:
 */
public class ChannelMDCommandDriver implements MDCommandDriver<MDCommand.Channel, RegisterInfo> {


    @Override
    public MDCommand.Channel[] getSupportCommands() {
        return MDCommand.Channel.values();
    }

    @Override
    public CommandResponse<JSON> chooseAndDeal(@NotNull MDCommand.Channel command,
                                               @NotNull SocketProxy<RegisterInfo> proxy,
                                               @NotNull Map<String, String> headers,
                                               @Nullable JSON data) throws ProtocolUnSupportException, CommandExecException, ClientConnectException {
        //command socketProxy headers must not be null.
        Assert.notNullAll(command,proxy,headers);
        CommandResponse<JSON> result = null;
        switch (command){
            case CHANNEL_CREATE:
                result = doChannelCreate(command,proxy,headers,data);
                break;
            case CHANNEL_JOIN:
                result = doChannelJoin(command,proxy,headers,data);
                break;
            case CHANNEL_QUIT:
                result = doChannelQuit(command,proxy,headers,data);
                break;
            default:
                throw new ProtocolUnSupportException("Protocol ["+command.getCommand()+"] UnSupport.");
        }
        return result;
    }

    /**
     * Channel 创建命令，
     * 要求当前socket为free态，且data为客户端注册的 CarRegisterInfo.
     * 创建channel没有要通知的客户端.
     * @param command
     * @param proxy
     * @param headers
     * @param data
     * @return
     */
    private CommandResponse<JSON> doChannelCreate(MDCommand.Channel command,
                                                  SocketProxy<RegisterInfo> proxy,
                                                  Map<String, String> headers,
                                                  JSON data) throws CommandExecException{
        //要求当前客户端没有BIND任何channel.
        if(proxy.getState() == SocketState.BIND){
            throw new CommandExecException("Client's state is already in BIND state!");
        }
        //要求data为客户端注册的 CarRegisterInfo.
        Assert.notNull(data);
        //要求当前socket为free态
        Assert.equals(proxy.getState(), SocketState.FREE);
        //获取channel's creators registerInfo
        RegisterInfo registerInfo = JSON.parseObject(data.toJSONString(), proxy.getRegInfoClass());
        if(!registerInfo.checkValid()){
            throw new CommandExecException("Protocol [" + command.getCommand() + "] required RegisterInfo is Valid." );
        }
        //创建working态channel，分配channel资源.
        Channel channel = new Channel(MindManger.idWorker.nextId());
        channel.clientJoin(proxy, registerInfo);
        /*创建channel线程，run.. */
        PoolUtil.execute(channel);

        //修改socketProxy状态BIND, 移除MindManager对于freeSocket的管理.
        proxy.bindChannel(channel,registerInfo);

        //将channel交由MindManager管理，引用备份working态的channel.
        MindManger.channels.put(channel.getChannelId(),channel);

        //构建响应信息
        ChannelCreateDTO channelCreateDTO = new ChannelCreateDTO(
                channel.getChannelId(),
                registerInfo.getIdentity(),
                getNetAddress(), MindManger.tcpPort);
        return CommandResponse.success(channelCreateDTO);
    }


    /**
     * channel 加入命令，
     * 创建信息共享channel的客户端分享channelId，以及channel的注册地址到车机伙伴
     * 车及伙伴创建channel注册地址的socket连接，进行channel的加入.
     * channel加入需要已经存在的通知客户端.
     * @param command
     * @param proxy
     * @param headers
     * @param data
     * @return
     * @throws CommandExecException
     */
    private CommandResponse<JSON> doChannelJoin(MDCommand.Channel command,
                                                SocketProxy<RegisterInfo> proxy,
                                                Map<String, String> headers,
                                                JSON data) throws CommandExecException{
        //要求当前客户端没有BIND任何channel.
        if(proxy.getState() == SocketState.BIND){
            throw new CommandExecException("Client's state is already in BIND state!");
        }
        //要求data为客户端注册的 CarRegisterInfo.
        Assert.notNull(data);
        //要求当前socket为free态
        Assert.equals(proxy.getState(), SocketState.FREE);
        //注册信息解析 校验.
        ChannelJoin channelJoin = ChannelJoin.parse((JSONObject) data,proxy.getRegInfoClass());
        if(!channelJoin.checkValid()){
            throw new CommandExecException("Protocol [" + command.getCommand() + "] required RegisterInfo and channelId is Valid." );
        }
        //检测channel存在性(channel状态为WORKING态)
        Channel channel = MindManger.channels.get(channelJoin.getChannelId());
        if(channel == null || channel.getState() != ChannelState.WORKING){
            throw new CommandExecException("Protocol [" + command.getCommand() + "] channel is inValid or has been destroy." );
        }
        //加入channel线程.
        channel.clientJoin(proxy,channelJoin.getRegisterInfo());

        //socketProxy绑定channel信息.
        proxy.bindChannel(channel,channelJoin.getRegisterInfo());

        //构建响应信息
        ChannelJoinDTO channelJoinDTO = new ChannelJoinDTO(
                channel.getChannelId(),
                channelJoin.getRegisterInfo().getIdentity(),
                getNetAddress(), MindManger.tcpPort);
        return CommandResponse.success(channelJoinDTO);
    }


    /**
     * channel 退出命令
     * 客户端退出其加入的channel，取消对channel的绑定状态，
     * 同时抹去在该channel中的注册信息，以及消息池中的上报共享的信息
     * @param command
     * @param proxy
     * @param headers
     * @param data
     * @return
     * @throws CommandExecException
     */
    private CommandResponse<JSON> doChannelQuit(MDCommand.Channel command,
                                                SocketProxy<RegisterInfo> proxy,
                                                Map<String, String> headers,
                                                JSON data) throws CommandExecException{
        //要求当前客户端没有BIND任何channel.
        if(proxy == null || proxy.getState() != SocketState.BIND){
            throw new CommandExecException("Client's has not BIND anyone channel!");
        }
        //要求data为客户端注册的 CarRegisterInfo.
        Assert.notNull(data,command.getCommand() + " param missed.");
        //检测传输数据的有效性.
        ChannelQuit channelQuit = JSON.parseObject(data.toJSONString(), ChannelQuit.class);
        if(!channelQuit.checkValid()){
            throw new CommandExecException("Protocol [" + command.getCommand() + "] required channelId is Valid." );
        }
        //检测channel存在性(channel状态为WORKING态)
        Channel channel = MindManger.channels.get(channelQuit.getChannelId());
        if(channel == null || channel.getState() != ChannelState.WORKING){
            throw new CommandExecException("Protocol [" + command.getCommand() + "] channel is inValid or has been destroy." );
        }
        //socketProxy取消channel绑定, 内部会调用channel 取消socketProxy的注册,
        //同时socket被置位FREE态, 等待重新加入channel或者超时回收.
        proxy.changeToFree();
        return CommandResponse.success(null);
    }

}
